import json
import os
import re
from urllib.parse import urljoin, urlparse

from channels.db import database_sync_to_async
from channels.layers import get_channel_layer
from django.conf import settings
from django.db import OperationalError
from django.http import HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.views import View
from django.views.decorators.cache import cache_page
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import TemplateView

from eventyay.base.models import SystemLog, Event
from eventyay.base.models.auth import ShortToken
from eventyay.base.models.room import AnonymousInvite


class SourceCache:
    @cached_property
    def source(self):
        wapath = os.path.normpath(
            os.path.join(os.path.dirname(__file__), "../../../static/webapp/index.html")
        )
        try:
            with open(wapath) as f:
                return f.read()
        except OSError:
            return f"<!-- {wapath} not found --><body></body>"


sh = SourceCache()


class ManifestView(View):
    def get(self, request, *args, **kwargs):
        event_domain = re.sub(r":\d+$", "", request.get_host())
        event = get_object_or_404(Event, domain=event_domain)
        # TODO: Allow to parametrize colors and logos
        source = {
            "name": event.title,
            "short_name": event.title,
            "theme_color": "#180044",
            "icons": [
                {
                    "src": "/eventyay-logo.192.png",
                    "type": "image/png",
                    "sizes": "192x192",
                },
                {
                    "src": "/eventyay-logo.512.png",
                    "type": "image/png",
                    "sizes": "512x512",
                },
                {"src": "/eventyay-logo.svg", "sizes": "192x192 512x512"},
            ],
            "start_url": ".",
            "display": "standalone",
            "background_color": "#000000",
        }
        return JsonResponse(source)


class AppView(View):
    """
    This view renders the main HTML. It is not used during development but only during production usage.
    """

    @cached_property
    def _has_separate_short_domain(self):
        shorturl_host = urlparse(settings.SHORT_URL).hostname
        siteurl_host = urlparse(settings.SITE_URL).hostname
        if shorturl_host != siteurl_host:
            return shorturl_host
        return False

    def get(self, request, *args, **kwargs):
        # Is this an anonymous invite to a room?
        short_host = self._has_separate_short_domain
        event_domain = re.sub(r":\d+$", "", request.get_host())
        if short_host and event_domain == short_host:
            # The sysadmin has set up a separate domain for short URLs
            if request.path == "/":
                # This must be a 200, not a 302 or 404, so the domain is considered "active"
                # by our auto-SSL setup in the eventyay.events production deployment
                return render(request, "live/short_domain_index.html")
            else:
                try:
                    invite = AnonymousInvite.objects.get(
                        expires__gte=now(),
                        short_token=request.path[1:],
                    )
                except AnonymousInvite.DoesNotExist:
                    return render(request, "live/short_domain_invalid.html", status=404)
                return redirect(
                    urljoin(
                        request.scheme + "://" + invite.event.domain,
                        f"/standalone/{invite.room_id}/anonymous#invite={invite.short_token}",
                    )
                )
        elif not short_host and len(request.path) == 7:
            # The sysadmin has not set up a separate domain for short URLs
            try:
                invite = AnonymousInvite.objects.get(
                    expires__gte=now(),
                    short_token=request.path[1:],
                )
                return redirect(
                    urljoin(
                        request.scheme + "://" + invite.event.domain,
                        f"/standalone/{invite.room_id}/anonymous#invite={invite.short_token}",
                    )
                )
            except AnonymousInvite.DoesNotExist:
                # We do not show a 404 since theoretically this could be a vlaid path recognized by
                # the frontend router.
                pass
        try:
            event = get_object_or_404(Event, domain=event_domain)
        except OperationalError:
            # We use connection pooling, so if the database server went away since the last connection
            # terminated, Django won't know and we'll get an OperationalError. We just silently re-try
            # once, since Django will then use a new connection.
            event = get_object_or_404(Event, domain=event_domain)
        source = sh.source
        source = re.sub(
            "<title>[^<]*</title>",
            f"<title>{event.title}</title>",
            source,
            re.IGNORECASE | re.MULTILINE,
        )
        source = source.replace(
            "<body>",
            "<script>window.eventyay={}</script><body>".format(
                json.dumps(
                    {
                        "api": {
                            "base": reverse("api:root", kwargs={"event_id": event.id}),
                            "socket": "{}://{}/ws/event/{}/".format(
                                settings.WEBSOCKET_PROTOCOL,
                                request.get_host(),
                                event.pk,
                            ),
                            "upload": reverse("storage:upload"),
                            "scheduleImport": reverse("storage:schedule_import"),
                            "systemlog": reverse("live:systemlog"),
                        },
                        "features": event.feature_flags,
                        "externalAuthUrl": event.external_auth_url,
                        "locale": event.locale,
                        "date_locale": event.config.get("date_locale", "en-ie"),
                        "theme": event.config.get("theme", {}),
                        "video_player": event.config.get("video_player", {}),
                        "mux": event.config.get("mux", {}),
                    }
                )
            ),
        )
        if event.config.get("theme", {}).get("css", ""):
            source = source.replace(
                "<body>",
                "<link rel='stylesheet' href='{}'><body>".format(
                    reverse("live:css.custom")
                ),
            )

        source = re.sub("<html[^>]*>", f'<html lang="{event.locale}">', source)

        r = HttpResponse(source, content_type="text/html")
        if "cross-origin-isolation" in event.feature_flags:
            r["Cross-Origin-Resource-Policy"] = "cross-origin"
            r["Cross-Origin-Embedder-Policy"] = "require-corp"
            r["Cross-Origin-Opener-Policy"] = "same-origin"
        return r


class HealthcheckView(View):
    """
    This view renders the main HTML. It is not used during development but only during production usage.
    """

    async def get(self, request, *args, **kwargs):
        await get_channel_layer().send("healthcheck_channel", {"type": "healthcheck"})
        await database_sync_to_async(Event.objects.count)()
        return HttpResponse("OK")


@method_decorator(cache_page(1 if settings.DEBUG else 60), name="dispatch")
class CustomCSSView(View):
    def get(self, request, *args, **kwargs):
        event_domain = re.sub(r":\d+$", "", request.get_host())
        event = get_object_or_404(Event, domain=event_domain)
        source = event.config.get("theme", {}).get("css", "")
        return HttpResponse(source, content_type="text/css")


@method_decorator(cache_page(1 if settings.DEBUG else 60), name="dispatch")
class BBBCSSView(TemplateView):
    template_name = "live/bbb.css"
    content_type = "text/css"

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data()
        event_domain = re.sub(r":\d+$", "", self.request.get_host())
        ctx["event"] = get_object_or_404(Event, domain=event_domain)
        return ctx


class ShortTokenView(View):
    def get(self, request, token):
        event_domain = re.sub(r":\d+$", "", self.request.get_host())
        event = get_object_or_404(Event, domain=event_domain)
        try:
            st = ShortToken.objects.get(short_token=token, event=event)
            return redirect(f"/#token={st.long_token}")
        except ShortToken.DoesNotExist:
            return HttpResponse(
                "Unknown access token. Please check that you clicked the correct link."
            )


class SystemLogView(View):
    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    @cached_property
    def event(self):
        event_domain = re.sub(r":\d+$", "", self.request.get_host())
        return get_object_or_404(Event, domain=event_domain)

    def post(self, request, *args, **kwargs):
        data = json.loads(request.body)
        SystemLog.objects.create(
            event=self.event,
            module=data.get("module"),
            message=data.get("message"),
            trace=data.get("trace"),
        )
        return JsonResponse({}, status=201)
